![別アカウントのS3バケットのイベントに基づいて、Lambdaを起動してS3 Syncしてみた](https://devio2023-media.developers.io/wp-content/uploads/2019/05/amazon-s3.png)
別アカウントのS3バケットのイベントに基づいて、Lambdaを起動してS3 Syncしてみた
データアナリティクス事業本部の笠原です。
今回は別アカウントのS3バケットのイベントから、こちらのアカウントのLambdaを起動して、S3 Syncを使ってバケット間のオブジェクト同期を試してみました。
3行まとめ
- Lambdaはs3 syncが使えるようにaws cli v2をlayerで導入する
- Lambda関数のリソースベースのアクセス権限ポリシーを更新して、S3呼び出しアクセス許可を設定する
- Lambda関数を呼び出す、S3イベント通知を作成する
構成図
今回はこんな感じの環境にいます。 送信元バケットから、送信先バケットに対して s3 sync コマンドを Lambda関数上で実行します。 送信元バケットと送信先バケットは、別のAWSアカウントになります。 また、Lambda関数は今回送信先バケット側に存在します。
バケット作成
送信元バケット、送信先バケットを事前に作成しておきます。
今回は、レプリケーションではないのでバージョニングは無効のままにしておきます。
- 同一リージョン (今回はap-northeast-1) で作成
- バージョニングは無効化
- パブリックアクセスブロック有効化
Lambda関数の作成
Lambda関数ば送信元バケットと同じAWSリージョンに存在する必要があります。
今回は ap-northeast-1 で作成します。
ランタイムは「Amazon Linux 2でユーザー独自のブートストラップを提供する」にします。
関数ができたら、ファイルを修正します。
bootstrap.sample
はリネームしてbootstrap
にします。s3sync.sh
を作成します。- 忘れずに Deploy します。
s3sync.sh
の内容はこんな感じです。
function handler () { EVENT_DATA=$1 aws s3 sync s3://${SOURCE_BUCKET}/ s3://${TARGET_BUCKET}/ }
環境変数 SOURCE_BUCKET
及び TARGET_BUCKET
に、送信元バケット名及び送信先バケット名を登録しておきましょう。
また、ランタイム設定にてハンドラを s3sync.handler
に変更しておきましょう。
IAMロールにS3アクセスポリシーをアタッチ
作成したLambda関数のIAMロールには、S3アクセスポリシーを忘れずにアタッチしましょう。
今回はAWSマネージドポリシーの AmazonS3FullAccess
をIAMロールにアタッチします。
LayerでAWS CLIを導入
今回はAWS CLIをLambda Layerで導入してみます。
DockerコンテナにてAWS CLIを展開し必要なファイルをlayerに持たせておきます。
Dockerfileはこんな感じで用意します。
FROM amazonlinux:latest as awslinux ARG LAMBDA_LAYER_BASE=/opt ARG AWS_FOLDER_NAME=awscliv2 ARG AWS_DIR=${LAMBDA_LAYER_BASE}/${AWS_FOLDER_NAME} ARG BIN_DIR=${LAMBDA_LAYER_BASE}/bin WORKDIR /root RUN yum update -y && yum install -y \ unzip \ zip \ wget \ curl \ tar \ gzip ADD https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip ./awscliv2.zip RUN unzip awscliv2.zip RUN ./aws/install -i ${AWS_DIR} -b ${BIN_DIR} # Reduce the size by removing unnecessary files RUN find ${AWS_DIR} -type d -name examples -exec rm -r {} + \ && find ${AWS_DIR} -type f -name aws_completer -delete \ && find ${BIN_DIR} -type l -name aws_completer -delete # Confirm the installation RUN ${BIN_DIR}/aws --version # Archive # AWS Lambda Layers are extracted to the /opt directory in the function execution environment WORKDIR ${LAMBDA_LAYER_BASE} RUN zip -r --symlinks layer.zip ${AWS_FOLDER_NAME} bin
また、bootstrap もこんな感じで用意します。
#!/bin/sh set -euo pipefail # Initialization - load function handler source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh" # Processing while true do HEADERS="$(mktemp)" # Get an event. The HTTP request will block until one is received EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next") # Extract request ID by scraping response headers received above REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2) # Run the handler function from the script RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA") # Send the response curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" -d "$RESPONSE" done
build.sh
と deploy.sh
はこんな感じです。
#!/bin/bash # Exit immediately if a command exits with a non-zero status. set -euo pipefail FILE='layer.zip' TAG='aws-cli-lambda-layer' rm -rf ${FILE} docker build -t ${TAG} . docker cp $(docker run -d ${TAG} false):/opt/layer.zip ${FILE} zip ${FILE} bootstrap docker rm -f $(docker run -d ${TAG} false)
#!/bin/bash Profile=$1 aws lambda publish-layer-version --layer-name awscliv2-layer --zip-file fileb://layer.zip --profile ${Profile}
用意できたら、 build.sh
を実行して layer.zip
を作成して、 deploy.sh
を実行してAWSに layer.zip
を登録します。
レイヤー名は awscliv2-layer
になります。
登録したら、Lambda関数にレイヤーを追加します。
注意点としては、カスタムランタイムの場合レイヤーの選択肢に出てこなかったので、作成したレイヤーのARNを指定すると追加できます。
S3の呼び出しアクセス許可を設定
作成したLambda関数は、別アカウントのS3イベントにて呼び出してもらう必要があるので、そのための準備を行います。
まずは、Lambda関数にS3の呼び出しアクセス許可を設定します。
「設定」タブの「アクセス権限」メニューから、「リソースベースのポリシー」にある「アクセス権限の追加」をクリックします。この中のポリシーステートメントでアクセス権限を追加していきます。
- 「AWSのサービス」をクリック
- サービス: s3
- ステートメントID: 適宜設定します。今回は
policy-statement-1
にしました。 - プリンシパル: デフォルト値のままです。
- ソースアカウント: 送信元バケットのアカウントID
- ソースARN: 送信元バケットのARN
arn:aws:s3:::<バケット名>
の形になっています。
- アクション:
lambda:invokeFunction
を選択
Lambda関数を呼び出すS3イベント通知を作成
次に、送信元バケットのイベント通知を設定します。
送信元バケットのプロパティから、「イベント通知」の「イベント通知の作成」をクリックします。
以下のように設定します。
- イベント名: 適宜設定します。今回は
invokeS3sync
としました。 - プレフィックス/サフィックス: 今回は空のままとします。
- イベントタイプ: 今回は
すべてのオブジェクト作成イベント
としました。 - 送信先
- Lambda関数
- 「Lambda関数ARNを入力」を選択し、呼び出すLambda関数のARNを設定
この変更を保存すれば、S3イベントに応じてLambda関数が起動します。
送信元バケットのバケットポリシー作成
今回はS3 syncコマンドでバケット間のオブジェクトを同期取得を行います。
そのためには、送信元バケットのポリシーで、Lambda関数のアクセスを許可してあげる必要があります。
送信元バケットのポリシーは以下のように設定しました。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "1", "Action": [ "s3:Get*", "s3:ListBucket" ], "Effect": "Allow", "Resource": [ "arn:aws:s3:::<送信元バケット>", "arn:aws:s3:::<送信元バケット>/*" ], "Principal": { "AWS": [ "arn:aws:iam::<送信先アカウントID>:role/<Lambda関数実行IAMロール>" ] } } ] }
動作確認
ここまでできたら動作確認します。
送信元バケットにファイルをアップロードし、しばらくすると送信先バケットにファイルが転送されていることが確認できます。
もし、しばらく待っても転送されていないようでしたら、Lambda関数の実行ログを確認してどこかエラーが発生していないか確認してみましょう。